﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using Microsoft.Practices.Unity;
using Wyn.PMS.Common.SecurityControl;
using Wyn.PMS.Common.Utils;
using Wyn.PMS.Entity.Entities;
using Wyn.PMS.Entity.IDataAccess;

namespace Wyn.PMS.Common.NightAudit
{
   
    public class DoNightAudit
    {

        private static readonly IUnityContainer container = UnityContainerService.GetContainer();
      
        public void Audit(HotelBizDay bizDay)
        {
            IHotelBizDayRepository bizDayRepository = container.Resolve<IHotelBizDayRepository>();

            IOrderRepository orderRepository = container.Resolve<IOrderRepository>();

            IAccountRepository accountRepository = container.Resolve<IAccountRepository>();

            IAccountRootRepository accountRootRepository = container.Resolve<IAccountRootRepository>();

            IRoomTypeRepository roomTypeRepository = container.Resolve<IRoomTypeRepository>();

            var transactionOptions = new System.Transactions.TransactionOptions();

            transactionOptions.IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted;

            transactionOptions.Timeout = new TimeSpan(0, 2, 30);
                
            //☆风险☆ 当夜审异常之后可能出现多人点击开始夜审的按钮！需控制
            if (bizDay.Status!=EnumHelper.GetCode(NightAuditCode.Status.E)
            || (bizDay.Status == EnumHelper.GetCode(NightAuditCode.Status.E)
                && bizDay.Steping == (int)NightAuditCode.Steping.ReadyCheck))
            {
                bizDay.Steping = (int) NightAuditCode.Steping.ReadyCheck;

                bizDay.Status = EnumHelper.GetCode(NightAuditCode.Status.S);

                bizDay.AuditTime = DateTime.Now;

                bizDayRepository.Update(bizDay);
            }
                
            string hotelId = bizDay.HotelID;

            DateTime todayBizDay = bizDay.BizDay;

            string inStatus = EnumHelper.GetCode(OrderStatus.I);

            string outStatus = EnumHelper.GetCode(OrderStatus.O);

            IList<RoomType> roomTypes;

            using (var scope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required, transactionOptions))
            {
                roomTypes = roomTypeRepository.BatchFind(hotelId, r => r.HotelID == hotelId && r.IsValid == true);
                scope.Complete();
            }
         
            Expression<Func<Order, bool>> orderExp = o =>o.HotelID == hotelId && (o.Status == inStatus || (o.Status ==outStatus && o.CheckOutBizDay==todayBizDay));

            IList<string> path = new List<string>{typeof (PriceDetail).Name};

            IList<Order> allOrders; 

            using (var scope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required, transactionOptions))
            {
                allOrders = orderRepository.BatchFind(bizDay.HotelID, orderExp);
                    
                scope.Complete();
            }
           
            var inOrders = allOrders.Where(o=>o.Status==inStatus).ToList();
               
            //DateTime toDayMax = todayBizDay.AddDays(1).AddSeconds(-1);
            //var inOrders = allOrders.Any() ? allOrders.Where(o => o.Status == inStatus && o.CheckinTime <= toDayMax).ToList() : null;
                
            // ☆ ---- 在住单复审检查 营业日 和 住单 [时租，白日，续住等等]  ---- ☆
            //var canntAudits = Precheck.CheckAuditOrders(inOrders,bizDay);

            //if (canntAudits != null && canntAudits.Any())
            //{
            //    throw new Exception("有未处理在住客人,不能进入夜审");
            //}

            if (bizDay.Status != EnumHelper.GetCode(NightAuditCode.Status.E))
            {
                bizDay.Steping = (int)NightAuditCode.Steping.RentRoll;
            }

            RentRoll rentRoll = new RentRoll();
            
            IDictionary<string,decimal> rollRoomPrice=null;

            if (inOrders.Count > 0)
            {
                rollRoomPrice = rentRoll.Roll(bizDay, inOrders, roomTypes);
            }
            else
            {
                if (bizDay.Status == EnumHelper.GetCode(NightAuditCode.Status.E)
                && bizDay.Steping == (int)NightAuditCode.Steping.RentRoll)
                {
                    bizDay.Status = EnumHelper.GetCode(NightAuditCode.Status.I);
                }
            }

            if (bizDay.Status != EnumHelper.GetCode(NightAuditCode.Status.E))
            {
                bizDay.Steping = (int)NightAuditCode.Steping.LedgerTotal;
            }

            string asX=EnumHelper.GetCode(AccountStatus.X);
            
            Expression<Func<Account, bool>> accountExp =
                r => r.HotelID == hotelId
                        && r.BizDay== todayBizDay
                        && r.Status != asX
                        && r.IsStrike == false;

            IList<Account> notRollAccounts;

            using (var scope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required, transactionOptions))
            {
            
                notRollAccounts = accountRepository.BatchFind(hotelId, accountExp);

                scope.Complete();
            }

            LedgerTotal ledgerTotal = new LedgerTotal();

            ledgerTotal.Total(bizDay, notRollAccounts);

            if (bizDay.Status != EnumHelper.GetCode(NightAuditCode.Status.E))
            {
                bizDay.Steping = (int)NightAuditCode.Steping.AccountRemain;
            }
           
            string ars_I = EnumHelper.GetCode(AccountStatus.I), ars_S = EnumHelper.GetCode(AccountStatus.S);
            
            string art_RA = EnumHelper.GetCode(AccountRootType.RA);

            Expression<Func<AccountRoot, bool>> accRootExp =
                r => r.HotelID == hotelId
                        && r.AccountRootType == art_RA
                        && (r.Status == ars_I || r.Status == ars_S);

            IList<string> arPath = new List<string>{ typeof(Account).Name };

            IList<AccountRoot> accountRoots;
            
            IList<Order> signOrders = new List<Order>();

            using (var scope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required, transactionOptions))
            {
                accountRoots = accountRootRepository.BatchIncludeFind(hotelId, accRootExp, arPath);

                var sAccountRoots = accountRoots.Where(a => a.Status == ars_S).ToList();

                if (sAccountRoots.Count > 0)
                {
                    var signOrderIds = sAccountRoots.Select(a => a.AccountRootID);

                    signOrders = orderRepository.BatchIncludeFind(bizDay.HotelID, o => signOrderIds.Contains(o.OrderID), path).ToList();
                }
                    
                scope.Complete();
            }

            accountRoots = BuildIncludeInfo(accountRoots, allOrders,signOrders);

            if (accountRoots != null && accountRoots.Count>0)
            {
                AccountRemain accountRemain = new AccountRemain();
                accountRemain.CalculateRemain(bizDay, accountRoots, notRollAccounts);
            }
            else
            {
                if (bizDay.Status == EnumHelper.GetCode(NightAuditCode.Status.E)
                && bizDay.Steping == (int)NightAuditCode.Steping.AccountRemain)
                {
                    bizDay.Status = EnumHelper.GetCode(NightAuditCode.Status.I);
                }
            }

            if (bizDay.Status != EnumHelper.GetCode(NightAuditCode.Status.E))
            {
                bizDay.Steping = (int)NightAuditCode.Steping.OperationStatus;
            }

            OperationStatus operationStatus = new OperationStatus();

            IList<NaOperationStatus> states = null;
           
            IList<Order> rollAccountOrders = GetRollAccountOrders(notRollAccounts, allOrders, orderRepository, hotelId);

            IDictionary<string, decimal> rtRoomFeeAccounts = SumRoomFeeAccounts(notRollAccounts, rollAccountOrders, roomTypes);
            
            states = operationStatus.Snapshot(bizDay, roomTypes,notRollAccounts,rollRoomPrice, rtRoomFeeAccounts,rollAccountOrders,allOrders);
            
            if (bizDay.Status != EnumHelper.GetCode(NightAuditCode.Status.E))
            {
                bizDay.Steping = (int)NightAuditCode.Steping.OperationAnalysis;
            }
                
            OperationAnalysis operationAnalysis = new OperationAnalysis();
                
            if (states!=null)
            {
                operationAnalysis.Total(bizDay, rtRoomFeeAccounts, notRollAccounts, roomTypes, states, allOrders.ToList(), rollAccountOrders);
            }
            else
            {
                if (bizDay.Status == EnumHelper.GetCode(NightAuditCode.Status.E)
                && bizDay.Steping == (int)NightAuditCode.Steping.OperationAnalysis)
                {
                    bizDay.Status = EnumHelper.GetCode(NightAuditCode.Status.I);
                }
            }

            bizDay.Steping = (int)NightAuditCode.Steping.CompleteAudit_A;
                
            CompleteAudit completeAudit = new CompleteAudit();

            completeAudit.Complete(notRollAccounts,bizDay);

            bizDay.Steping = (int)NightAuditCode.Steping.CompleteAudit_B;

            completeAudit.Complete(inOrders, bizDay);
           
            bizDay.Steping = (int)NightAuditCode.Steping.CompleteAudit_C;

            completeAudit.Complete(bizDay);
            
        }
        
        private IList<Order> GetRollAccountOrders(IList<Account> notRollAccounts, IList<Order> allOrders, IOrderRepository orderRepository,string hotelId)
        {
            var transactionOptions = new System.Transactions.TransactionOptions();

            transactionOptions.IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted;
            
            var notRollOrderIds= notRollAccounts.Select(o => o.CreateOrderID).Distinct();

            IList<Order> rollRoomTypeOrders = new List<Order>();

            IList<string> otherOrderIds = new List<string>();

            foreach (var orderId in notRollOrderIds)
            {
                var order = allOrders.FirstOrDefault(o => o.OrderID == orderId);

                if (order == null)
                {
                    otherOrderIds.Add(orderId);
                }
                else
                {
                    rollRoomTypeOrders.Add(order);
                }
            }

            if (otherOrderIds.Any())
            {
                IList<Order> otherOrders = new List<Order>();

                using (var scope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required, transactionOptions))
                {
                    otherOrders = orderRepository.BatchFind(hotelId, o => otherOrderIds.Contains(o.OrderID));

                    scope.Complete();
                }

                rollRoomTypeOrders = rollRoomTypeOrders.Union(otherOrders).ToList();
            }

            return rollRoomTypeOrders;
        }

        private IList<AccountRoot> BuildIncludeInfo(IList<AccountRoot> accountRoots, IList<Order> allOrders, IList<Order> signOrders)
        {
            foreach (var art in accountRoots)
            {
                art.Order = allOrders.FirstOrDefault(o => o.OrderID == art.AccountRootID);
                
                if (art.Order == null) art.Order = signOrders.FirstOrDefault(o => o.OrderID == art.AccountRootID);
            }
            return accountRoots;
        }

        private IDictionary<string, decimal> SumRoomFeeAccounts(IList<Account> notRollAccounts, IList<Order> rollRoomTypeOrders, IList<RoomType> roomTypes)
        {
            IDictionary<string, decimal> rtRoomFee = new Dictionary<string, decimal>();

            string CIT_HR = EnumHelper.GetCode(CheckInType.HR), CIT_DR = EnumHelper.GetCode(CheckInType.DR);

            foreach (var roomtype in roomTypes)
            {
                //旧版里使用此方法过滤NS房费 && o.CheckInType != EnumHelper.GetCode(CheckInType.HR) && o.CheckInType != EnumHelper.GetCode(CheckInType.DR)
                string roomTypeId = roomtype.RoomTypeID;

                IEnumerable<string> roomTypeOrderIds = rollRoomTypeOrders.Where(o => o.RoomTypeID == roomTypeId).Select(o => o.OrderID);

                decimal receipts = notRollAccounts
                    .Where(a => roomTypeOrderIds.Contains(a.CreateOrderID)
                                && !a.IsStrike
                                && a.CheckInType != CIT_HR
                                && a.CheckInType != CIT_DR
                                && (a.LedgerCode == LedgerCode.标准房租
                                   || a.LedgerCode == LedgerCode.加床))
                    .Sum(o => o.Balance);

                rtRoomFee.Add(roomTypeId, receipts);
            }
            
            return rtRoomFee;
        }
        
    }
}
